agora inbox for pgsql-hackers@postgresql.org  
help / color / mirror / Atom feed
[PATCH 2/4] zstd: support long distance mode in pg_dump/basebackup
457+ messages / 2 participants
[nested] [flat]

* [PATCH 2/4] zstd: support long distance mode in pg_dump/basebackup
@ 2022-03-27 16:55 Justin Pryzby <pryzbyj@telsasoft.com>
  0 siblings, 0 replies; 457+ messages in thread

From: Justin Pryzby @ 2022-03-27 16:55 UTC (permalink / raw)

First proposed here:
20220327205020.GM28503@telsasoft.com
---
 doc/src/sgml/protocol.sgml                    | 10 +++-
 doc/src/sgml/ref/pg_basebackup.sgml           |  4 +-
 doc/src/sgml/ref/pg_dump.sgml                 |  2 +
 src/backend/backup/basebackup_zstd.c          | 12 ++++
 src/bin/pg_basebackup/bbstreamer_zstd.c       | 13 +++++
 src/bin/pg_basebackup/t/010_pg_basebackup.pl  |  9 ++-
 src/bin/pg_dump/compress_zstd.c               |  5 ++
 src/bin/pg_dump/t/002_pg_dump.pl              |  3 +-
 src/bin/pg_verifybackup/t/008_untar.pl        |  8 +++
 src/bin/pg_verifybackup/t/010_client_untar.pl |  8 +++
 src/common/compression.c                      | 57 ++++++++++++++++++-
 src/include/common/compression.h              |  2 +
 12 files changed, 127 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 8b5e7b1ad7f..b11d9a6ba35 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2729,7 +2729,8 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
            level.  Otherwise, it should be a comma-separated list of items,
            each of the form <replaceable>keyword</replaceable> or
            <replaceable>keyword=value</replaceable>. Currently, the supported
-           keywords are <literal>level</literal> and <literal>workers</literal>.
+           keywords are <literal>level</literal>, <literal>long</literal> and
+           <literal>workers</literal>.
           </para>
 
           <para>
@@ -2746,6 +2747,13 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
            <literal>3</literal>).
           </para>
 
+          <para>
+           The <literal>long</literal> keyword enables long-distance matching
+           mode, for improved compression ratio, at the expense of higher memory
+           use.  Long-distance mode is supported only for
+           <literal>zstd</literal>.
+          </para>
+
           <para>
            The <literal>workers</literal> keyword sets the number of threads
            that should be used for parallel compression. Parallel compression
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index db3ad9cd5eb..79d3e657c32 100644
--- a/doc/src/sgml/ref/pg_basebackup.sgml
+++ b/doc/src/sgml/ref/pg_basebackup.sgml
@@ -424,8 +424,8 @@ PostgreSQL documentation
         level.  Otherwise, it should be a comma-separated list of items,
         each of the form <literal>keyword</literal> or
         <literal>keyword=value</literal>.
-        Currently, the supported keywords are <literal>level</literal>
-        and <literal>workers</literal>.
+        Currently, the supported keywords are <literal>level</literal>,
+        <literal>long</literal>, and <literal>workers</literal>.
         The detail string cannot be used when the compression method
         is specified as a plain integer.
        </para>
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 8de38e0fd0d..e81e35c13b3 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -681,6 +681,8 @@ PostgreSQL documentation
         as though it had been fed through <application>gzip</application>,
         <application>lz4</application>, or <application>zstd</application>;
         but the default is not to compress.
+        With zstd compression, <literal>long</literal> mode may improve the
+        compression ratio, at the cost of increased memory use.
        </para>
        <para>
         The tar archive format currently does not support compression at all.
diff --git a/src/backend/backup/basebackup_zstd.c b/src/backend/backup/basebackup_zstd.c
index ac6cac178a0..1bb5820c884 100644
--- a/src/backend/backup/basebackup_zstd.c
+++ b/src/backend/backup/basebackup_zstd.c
@@ -118,6 +118,18 @@ bbsink_zstd_begin_backup(bbsink *sink)
 						   compress->workers, ZSTD_getErrorName(ret)));
 	}
 
+	if ((compress->options & PG_COMPRESSION_OPTION_LONG_DISTANCE) != 0)
+	{
+		ret = ZSTD_CCtx_setParameter(mysink->cctx,
+									 ZSTD_c_enableLongDistanceMatching,
+									 compress->long_distance);
+		if (ZSTD_isError(ret))
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("could not set compression flag for %s: %s",
+						   "long", ZSTD_getErrorName(ret)));
+	}
+
 	/*
 	 * We need our own buffer, because we're going to pass different data to
 	 * the next sink than what gets passed to us.
diff --git a/src/bin/pg_basebackup/bbstreamer_zstd.c b/src/bin/pg_basebackup/bbstreamer_zstd.c
index fe17d6df4ef..fba391e2a0f 100644
--- a/src/bin/pg_basebackup/bbstreamer_zstd.c
+++ b/src/bin/pg_basebackup/bbstreamer_zstd.c
@@ -106,6 +106,19 @@ bbstreamer_zstd_compressor_new(bbstreamer *next, pg_compress_specification *comp
 					 compress->workers, ZSTD_getErrorName(ret));
 	}
 
+	if ((compress->options & PG_COMPRESSION_OPTION_LONG_DISTANCE) != 0)
+	{
+		ret = ZSTD_CCtx_setParameter(streamer->cctx,
+									 ZSTD_c_enableLongDistanceMatching,
+									 compress->long_distance);
+		if (ZSTD_isError(ret))
+		{
+			pg_log_error("could not set compression flag for %s: %s",
+						 "long", ZSTD_getErrorName(ret));
+			exit(1);
+		}
+	}
+
 	/* Initialize the ZSTD output buffer. */
 	streamer->zstd_outBuf.dst = streamer->base.bbs_buffer.data;
 	streamer->zstd_outBuf.size = streamer->base.bbs_buffer.maxlen;
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index b60cb78a0d5..4d130a7f944 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -139,7 +139,14 @@ SKIP:
 			'gzip:workers=3',
 			'invalid compression specification: compression algorithm "gzip" does not accept a worker count',
 			'failure on worker count for gzip'
-		],);
+		],
+		[
+			'gzip:long',
+			'invalid compression specification: compression algorithm "gzip" does not support long-distance mode',
+			'failure on long mode for gzip'
+		],
+	);
+
 	for my $cft (@compression_failure_tests)
 	{
 		my $cfail = quotemeta($client_fails . $cft->[1]);
diff --git a/src/bin/pg_dump/compress_zstd.c b/src/bin/pg_dump/compress_zstd.c
index cf85c3a4c93..49a877ce010 100644
--- a/src/bin/pg_dump/compress_zstd.c
+++ b/src/bin/pg_dump/compress_zstd.c
@@ -80,6 +80,11 @@ _ZstdCStreamParams(pg_compress_specification compress)
 	_Zstd_CCtx_setParam_or_die(cstream, ZSTD_c_compressionLevel,
 							   compress.level, "level");
 
+	if (compress.options & PG_COMPRESSION_OPTION_LONG_DISTANCE)
+		_Zstd_CCtx_setParam_or_die(cstream,
+								  ZSTD_c_enableLongDistanceMatching,
+								  compress.long_distance, "long");
+
 	return cstream;
 }
 
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 74f23ae7f74..bb898b06bb4 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -267,11 +267,12 @@ my %pgdump_runs = (
 		],
 	},
 
+	# Exercise long mode for test coverage
 	compression_zstd_plain => {
 		test_key       => 'compression',
 		compile_option => 'zstd',
 		dump_cmd       => [
-			'pg_dump', '--format=plain', '--compress=zstd',
+			'pg_dump', '--format=plain', '--compress=zstd:long',
 			"--file=$tempdir/compression_zstd_plain.sql.zst", 'postgres',
 		],
 		# Decompress the generated file to run through the tests.
diff --git a/src/bin/pg_verifybackup/t/008_untar.pl b/src/bin/pg_verifybackup/t/008_untar.pl
index 3007bbe8556..05754bc8ec7 100644
--- a/src/bin/pg_verifybackup/t/008_untar.pl
+++ b/src/bin/pg_verifybackup/t/008_untar.pl
@@ -49,6 +49,14 @@ my @test_configuration = (
 		'decompress_program' => $ENV{'ZSTD'},
 		'decompress_flags'   => ['-d'],
 		'enabled'            => check_pg_config("#define USE_ZSTD 1")
+	},
+	{
+		'compression_method' => 'zstd',
+		'backup_flags'       => [ '--compress', 'server-zstd:level=1,long' ],
+		'backup_archive'     => 'base.tar.zst',
+		'decompress_program' => $ENV{'ZSTD'},
+		'decompress_flags'   => ['-d'],
+		'enabled'            => check_pg_config("#define USE_ZSTD 1")
 	});
 
 for my $tc (@test_configuration)
diff --git a/src/bin/pg_verifybackup/t/010_client_untar.pl b/src/bin/pg_verifybackup/t/010_client_untar.pl
index f3aa0f59e29..ac51a174d14 100644
--- a/src/bin/pg_verifybackup/t/010_client_untar.pl
+++ b/src/bin/pg_verifybackup/t/010_client_untar.pl
@@ -50,6 +50,14 @@ my @test_configuration = (
 		'decompress_flags'   => ['-d'],
 		'enabled'            => check_pg_config("#define USE_ZSTD 1")
 	},
+	{
+		'compression_method' => 'zstd',
+		'backup_flags' => ['--compress', 'client-zstd:level=1,long'],
+		'backup_archive' => 'base.tar.zst',
+		'decompress_program' => $ENV{'ZSTD'},
+		'decompress_flags' => [ '-d' ],
+		'enabled' => check_pg_config("#define USE_ZSTD 1")
+	},
 	{
 		'compression_method' => 'parallel zstd',
 		'backup_flags'       => [ '--compress', 'client-zstd:workers=3' ],
diff --git a/src/common/compression.c b/src/common/compression.c
index 2d3e56b4d62..35a7cade645 100644
--- a/src/common/compression.c
+++ b/src/common/compression.c
@@ -12,7 +12,7 @@
  * Otherwise, a compression specification is a comma-separated list of items,
  * each having the form keyword or keyword=value.
  *
- * Currently, the only supported keywords are "level" and "workers".
+ * Currently, the supported keywords are "level", "long", and "workers".
  *
  * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
  *
@@ -38,6 +38,8 @@
 
 static int	expect_integer_value(char *keyword, char *value,
 								 pg_compress_specification *result);
+static bool expect_boolean_value(char *keyword, char *value,
+								 pg_compress_specification *result);
 
 /*
  * Look up a compression algorithm by name. Returns true and sets *algorithm
@@ -232,6 +234,11 @@ parse_compress_specification(pg_compress_algorithm algorithm, char *specificatio
 			result->workers = expect_integer_value(keyword, value, result);
 			result->options |= PG_COMPRESSION_OPTION_WORKERS;
 		}
+		else if (strcmp(keyword, "long") == 0)
+		{
+			result->long_distance = expect_boolean_value(keyword, value, result);
+			result->options |= PG_COMPRESSION_OPTION_LONG_DISTANCE;
+		}
 		else
 			result->parse_error =
 				psprintf(_("unrecognized compression option: \"%s\""), keyword);
@@ -289,6 +296,43 @@ expect_integer_value(char *keyword, char *value, pg_compress_specification *resu
 	return ivalue;
 }
 
+/*
+ * Parse 'value' as a boolean and return the result.
+ *
+ * If parsing fails, set result->parse_error to an appropriate message
+ * and return -1.  The caller must check result->parse_error to determine if
+ * the call was successful.
+ *
+ * Valid values are: yes, no, on, off, 1, 0.
+ *
+ * Inspired by ParseVariableBool().
+ */
+static bool
+expect_boolean_value(char *keyword, char *value, pg_compress_specification *result)
+{
+	if (value == NULL)
+		return true;
+
+	if (pg_strcasecmp(value, "yes") == 0)
+		return true;
+	if (pg_strcasecmp(value, "on") == 0)
+		return true;
+	if (pg_strcasecmp(value, "1") == 0)
+		return true;
+
+	if (pg_strcasecmp(value, "no") == 0)
+		return false;
+	if (pg_strcasecmp(value, "off") == 0)
+		return false;
+	if (pg_strcasecmp(value, "0") == 0)
+		return false;
+
+	result->parse_error =
+		psprintf(_("value for compression option \"%s\" must be a boolean"),
+				 keyword);
+	return false;
+}
+
 /*
  * Returns NULL if the compression specification string was syntactically
  * valid and semantically sensible.  Otherwise, returns an error message.
@@ -354,6 +398,17 @@ validate_compress_specification(pg_compress_specification *spec)
 						get_compress_algorithm_name(spec->algorithm));
 	}
 
+	/*
+	 * Of the compression algorithms that we currently support, only zstd
+	 * supports long-distance mode.
+	 */
+	if ((spec->options & PG_COMPRESSION_OPTION_LONG_DISTANCE) != 0 &&
+		(spec->algorithm != PG_COMPRESSION_ZSTD))
+	{
+		return psprintf(_("compression algorithm \"%s\" does not support long-distance mode"),
+						get_compress_algorithm_name(spec->algorithm));
+	}
+
 	return NULL;
 }
 
diff --git a/src/include/common/compression.h b/src/include/common/compression.h
index b48c173022e..38aae9dd873 100644
--- a/src/include/common/compression.h
+++ b/src/include/common/compression.h
@@ -27,6 +27,7 @@ typedef enum pg_compress_algorithm
 } pg_compress_algorithm;
 
 #define PG_COMPRESSION_OPTION_WORKERS		(1 << 0)
+#define PG_COMPRESSION_OPTION_LONG_DISTANCE	(1 << 1)
 
 typedef struct pg_compress_specification
 {
@@ -34,6 +35,7 @@ typedef struct pg_compress_specification
 	unsigned	options;		/* OR of PG_COMPRESSION_OPTION constants */
 	int			level;
 	int			workers;
+	bool		long_distance;
 	char	   *parse_error;	/* NULL if parsing was OK, else message */
 } pg_compress_specification;
 
-- 
2.34.1


--cM8PVdkeIMgUsrHS
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="0003-WIP-pg_dump-support-zstd-workers.patch"



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

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

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

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

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


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



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

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

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread

* [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState
@ 2026-03-23 20:27 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 457+ messages in thread

From: Álvaro Herrera @ 2026-03-23 20:27 UTC (permalink / raw)

This makes the data structure simpler, as we only need a single struct
which can be passed down to all functions.

Also, the inititalization and release sequences are a bit simpler
and there's a single location in charge.
---
 src/backend/commands/cluster.c   | 307 ++++++++++++++-----------------
 src/tools/pgindent/typedefs.list |   2 +-
 2 files changed, 135 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b4acaeb6b93..b86e600af41 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -113,15 +113,6 @@ typedef struct
 static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
 static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
 
-/*
- * Everything we need to call ExecInsertIndexTuples().
- */
-typedef struct IndexInsertState
-{
-	ResultRelInfo *rri;
-	EState	   *estate;
-} IndexInsertState;
-
 /* The WAL segment being decoded. */
 static XLogSegNo repack_current_segment = 0;
 
@@ -134,31 +125,27 @@ static XLogSegNo repack_current_segment = 0;
 /*
  * Information needed to apply concurrent data changes.
  */
-typedef struct ChangeDest
+typedef struct ChangeContext
 {
 	/* The relation the changes are applied to. */
-	Relation	rel;
-
-	/*
-	 * The following is needed to find the existing tuple if the change is
-	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
-	 * 'sk_argument' initialized.
-	 */
-	Relation	ident_index;
-	ScanKey		ident_key;
-	int			ident_key_nentries;
+	Relation	cc_rel;
 
 	/* Needed to update indexes of rel_dst. */
-	IndexInsertState *iistate;
+	ResultRelInfo *cc_rri;
+	EState	   *cc_estate;
 
 	/*
-	 * Sequential number of the file containing the changes.
-	 *
-	 * TODO This field makes the structure name less descriptive. Should we
-	 * rename it, e.g. to ChangeApplyInfo?
+	 * Existing tuples to UPDATE and DELETE are located via this index. We
+	 * keep the scankey in partially initialized state to avoid repeated work.
+	 * sk_argument is completed on the fly.
 	 */
-	int			file_seq;
-} ChangeDest;
+	Relation	cc_ident_index;
+	ScanKey		cc_ident_key;
+	int			cc_ident_key_nentries;
+
+	/* Sequential number of the file containing the changes. */
+	int			cc_file_seq;
+} ChangeContext;
 
 /*
  * Layout of shared memory used for communication between backend and the
@@ -278,29 +265,27 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd,
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
-static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple,
-									IndexInsertState *iistate);
+									ChangeContext *chgcxt);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
 						  TupleTableSlot *slot);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
-									   TupleTableSlot *src);
-static bool find_target_tuple(Relation rel, ChangeDest *dest,
+								  TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
 							  TupleTableSlot *locator,
 							  TupleTableSlot *received);
 static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeDest *dest,
+									   ChangeContext *chgcxt,
 									   bool done);
-static IndexInsertState *get_index_insert_state(Relation relation,
-												Oid ident_index_id,
-												Relation *ident_index_p);
-static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
-								  int *nentries);
-static void free_index_insert_state(IndexInsertState *iistate);
+static void initialize_change_context(ChangeContext *chgcxt,
+									  Relation relation,
+									  Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
 static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
@@ -2871,13 +2856,13 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
  * Apply changes stored in 'file'.
  */
 static void
-apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 {
-	ConcurrentChangeKind	kind = '\0';
-	Relation	rel = dest->rel;
-	TupleTableSlot	*spilled_tuple;
-	TupleTableSlot	*old_update_tuple;
-	TupleTableSlot  *ondisk_tuple;
+	ConcurrentChangeKind kind = '\0';
+	Relation	rel = chgcxt->cc_rel;
+	TupleTableSlot *spilled_tuple;
+	TupleTableSlot *old_update_tuple;
+	TupleTableSlot *ondisk_tuple;
 	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
 
@@ -2924,12 +2909,12 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		/*
 		 * Just before an UPDATE or DELETE, we must update the command
-		 * counter, because the change could refer to a tuple that we
-		 * have just inserted; and before an INSERT, we have to do this
-		 * also if the previous command was either update or delete.
+		 * counter, because the change could refer to a tuple that we have
+		 * just inserted; and before an INSERT, we have to do this also if the
+		 * previous command was either update or delete.
 		 *
-		 * With this approach we don't spend so many CCIs for long
-		 * strings of only INSERTs, which can't affect one another.
+		 * With this approach we don't spend so many CCIs for long strings of
+		 * only INSERTs, which can't affect one another.
 		 */
 		if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
 			(kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
@@ -2946,22 +2931,22 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(rel, spilled_tuple, dest->iistate);
+			apply_concurrent_insert(rel, spilled_tuple, chgcxt);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool	found;
+			bool		found;
 
 			/* Find the tuple to be deleted */
-			found = find_target_tuple(rel, dest, spilled_tuple, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 			apply_concurrent_delete(rel, ondisk_tuple);
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
-			TupleTableSlot	*key;
-			bool	found;
+			TupleTableSlot *key;
+			bool		found;
 
 			if (have_old_tuple)
 				key = old_update_tuple;
@@ -2969,20 +2954,20 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
 				key = spilled_tuple;
 
 			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(rel, dest, key, ondisk_tuple);
+			found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
 			if (!found)
 				elog(ERROR, "failed to find target tuple");
 
 			/*
 			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for
-			 * the new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute
-			 * hasn't changed.)
+			 * relation's toast. Copy the corresponding TOAST pointers for the
+			 * new relation from the existing tuple. (The fact that we
+			 * received a TOAST pointer here implies that the attribute hasn't
+			 * changed.)
 			 */
 			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
 
-			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, dest->iistate);
+			apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -3006,7 +2991,7 @@ apply_concurrent_changes(BufFile *file, ChangeDest *dest)
  */
 static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	List	   *recheck;
 
@@ -3019,8 +3004,8 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 	 * action that already happened, we have no use for the recheck list of
 	 * indexes returned, so just free it.  XXX or maybe just leave it?
 	 */
-	recheck = ExecInsertIndexTuples(iistate->rri,
-									iistate->estate,
+	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+									chgcxt->cc_estate,
 									0,
 									slot,
 									NIL, NULL);
@@ -3028,7 +3013,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
 
-	ResetPerTupleExprContext(iistate->estate);
+	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -3038,7 +3023,7 @@ apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 static void
 apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 						TupleTableSlot *ondisk_tuple,
-						IndexInsertState *iistate)
+						ChangeContext *chgcxt)
 {
 	LockTupleMode lockmode;
 	TM_FailureData tmfd;
@@ -3065,13 +3050,13 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(iistate->rri,
-										iistate->estate,
+		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
+										chgcxt->cc_estate,
 										flags,
 										spilled_tuple,
 										NIL, NULL);
 		list_free(recheck);
-		ResetPerTupleExprContext(iistate->estate);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -3135,19 +3120,19 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	ExecForceStoreHeapTuple(tup, slot, false);
 
 	/*
-	 * Next, read any attributes we stored separately into the tts_values array
-	 * elements expecting them, if any.  This matches store_change.
+	 * Next, read any attributes we stored separately into the tts_values
+	 * array elements expecting them, if any.  This matches store_change.
 	 */
 	BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
 	if (natt_ext > 0)
 	{
-		TupleDesc desc = slot->tts_tupleDescriptor;
+		TupleDesc	desc = slot->tts_tupleDescriptor;
 
 		for (int i = 0; i < desc->natts; i++)
 		{
 			CompactAttribute *attr = TupleDescCompactAttr(desc, i);
-			varlena	   *varlen;
-			alignas(uint32) varlena	varhdr;
+			varlena    *varlen;
+			alignas(uint32) varlena varhdr;
 			void	   *value;
 			Size		varlensz;
 
@@ -3219,10 +3204,10 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
  * not found, return false.
  */
 static bool
-find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
 				  TupleTableSlot *retrieved)
 {
-	Form_pg_index idx = dest->ident_index->rd_index;
+	Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
 	IndexScanDesc scan;
 	bool		retval;
 
@@ -3233,9 +3218,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	 *
 	 * Use the incoming tuple to finalize the scan key.
 	 */
-	for (int i = 0; i < dest->ident_key_nentries; i++)
+	for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
 	{
-		ScanKey		entry = &dest->ident_key[i];
+		ScanKey		entry = &chgcxt->cc_ident_key[i];
 		AttrNumber	attno = idx->indkey.values[i];
 
 		entry->sk_argument = locator->tts_values[attno - 1];
@@ -3243,9 +3228,9 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
 	}
 
 	/* XXX no instrumentation for now */
-	scan = index_beginscan(rel, dest->ident_index, GetActiveSnapshot(),
-						   NULL, dest->ident_key_nentries, 0);
-	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+						   NULL, chgcxt->cc_ident_key_nentries, 0);
+	index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
 	retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
 	index_endscan(scan);
 
@@ -3260,7 +3245,7 @@ find_target_tuple(Relation rel, ChangeDest *dest, TupleTableSlot *locator,
  * are far too similar to each other.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3293,7 +3278,7 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == dest->file_seq)
+		if (last_exported == chgcxt->cc_file_seq)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3301,117 +3286,99 @@ process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
 	ConditionVariableCancelSleep();
 
 	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(file, dest);
+	apply_concurrent_changes(file, chgcxt);
 
 	BufFileClose(file);
 
 	/* Get ready for the next file. */
-	dest->file_seq++;
+	chgcxt->cc_file_seq++;
 }
 
 /*
- * Initialize IndexInsertState for index specified by ident_index_id.
- *
- * While doing that, also return the identity index in *ident_index_p.
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
  */
-static IndexInsertState *
-get_index_insert_state(Relation relation, Oid ident_index_id,
-					   Relation *ident_index_p)
+static void
+initialize_change_context(ChangeContext *chgcxt,
+						  Relation relation, Oid ident_index_id)
 {
-	EState	   *estate;
-	IndexInsertState *result;
-	Relation	ident_index = NULL;
+	chgcxt->cc_rel = relation;
 
-	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
-	estate = CreateExecutorState();
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	chgcxt->cc_estate = CreateExecutorState();
 
-	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-	InitResultRelInfo(result->rri, relation, 0, 0, 0);
-	ExecOpenIndices(result->rri, false);
+	chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-	 * Find the relcache entry of the identity index so that we spend no extra
-	 * effort to open / close it.
+	 * The table's relcache entry already has the relcache entry for the
+	 * identity index; find that.
 	 */
-	for (int i = 0; i < result->rri->ri_NumIndices; i++)
+	chgcxt->cc_ident_index = NULL;
+	for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
 	{
 		Relation	ind_rel;
 
-		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
 		if (ind_rel->rd_id == ident_index_id)
-			ident_index = ind_rel;
+		{
+			chgcxt->cc_ident_index = ind_rel;
+			break;
+		}
 	}
-	if (ident_index == NULL)
+	if (chgcxt->cc_ident_index == NULL)
 		elog(ERROR, "failed to find identity index");
 
-	/* Only initialize fields needed by ExecInsertIndexTuples(). */
-	result->estate = estate;
-
-	*ident_index_p = ident_index;
-	return result;
-}
-
-/*
- * Build scan key to process logical changes.
- */
-static ScanKey
-build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
-{
-	Relation	ident_idx_rel;
-	Form_pg_index ident_idx;
-	int			n,
-				i;
-	ScanKey		result;
-
-	Assert(OidIsValid(ident_idx_oid));
-	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
-	ident_idx = ident_idx_rel->rd_index;
-	n = ident_idx->indnkeyatts;
-	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
-	for (i = 0; i < n; i++)
+	/* Set up for scanning said identity index */
 	{
-		ScanKey		entry;
-		Oid			opfamily,
-					opcintype,
-					opno,
-					opcode;
+		Form_pg_index indexForm;
 
-		entry = &result[i];
+		indexForm = chgcxt->cc_ident_index->rd_index;
+		chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+		chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+		for (int i = 0; i < indexForm->indnkeyatts; i++)
+		{
+			ScanKey		entry;
+			Oid			opfamily,
+						opcintype,
+						opno,
+						opcode;
 
-		opfamily = ident_idx_rel->rd_opfamily[i];
-		opcintype = ident_idx_rel->rd_opcintype[i];
-		opno = get_opfamily_member(opfamily, opcintype, opcintype,
-								   BTEqualStrategyNumber);
+			entry = &chgcxt->cc_ident_key[i];
 
-		if (!OidIsValid(opno))
-			elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+			opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+			opno = get_opfamily_member(opfamily, opcintype, opcintype,
+									   BTEqualStrategyNumber);
+			if (!OidIsValid(opno))
+				elog(ERROR, "failed to find = operator for type %u", opcintype);
+			opcode = get_opcode(opno);
+			if (!OidIsValid(opcode))
+				elog(ERROR, "failed to find = operator for operator %u", opno);
 
-		opcode = get_opcode(opno);
-		if (!OidIsValid(opcode))
-			elog(ERROR, "failed to find = operator for operator %u", opno);
-
-		/* Initialize everything but argument. */
-		ScanKeyInit(entry,
-					i + 1,
-					BTEqualStrategyNumber, opcode,
-					(Datum) NULL);
-		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+			/* Initialize everything but argument. */
+			ScanKeyInit(entry,
+						i + 1,
+						BTEqualStrategyNumber, opcode,
+						(Datum) NULL);
+			entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+		}
 	}
-	index_close(ident_idx_rel, AccessShareLock);
 
-	*nentries = n;
-	return result;
+	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
 }
 
 static void
-free_index_insert_state(IndexInsertState *iistate)
+release_change_context(ChangeContext *chgcxt)
 {
-	ExecCloseIndices(iistate->rri);
-	FreeExecutorState(iistate->estate);
-	pfree(iistate->rri);
-	pfree(iistate);
+	ExecCloseIndices(chgcxt->cc_rri);
+	FreeExecutorState(chgcxt->cc_estate);
+	/* XXX are these pfrees necessary? */
+	pfree(chgcxt->cc_rri);
+	pfree(chgcxt->cc_ident_key);
 }
 
 static void
@@ -3450,7 +3417,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	Relation   *ind_refs,
 			   *ind_refs_p;
 	int			nind;
-	ChangeDest	chgdst;
+	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
 	lockmode_old = ShareUpdateExclusiveLock;
@@ -3496,12 +3463,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 				errmsg("identity index missing on the new relation"));
 
 	/* Gather information to apply concurrent changes. */
-	chgdst.rel = NewHeap;
-	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
-											&chgdst.ident_index);
-	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
-										  &chgdst.ident_key_nentries);
-	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3523,7 +3485,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, false);
+	process_concurrent_changes(end_of_wal, &chgcxt, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3608,7 +3570,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Apply the concurrent changes again. Indicate that the decoding worker
 	 * won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgdst, true);
+	process_concurrent_changes(end_of_wal, &chgcxt, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3659,8 +3621,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	pfree(chgdst.ident_key);
-	free_index_insert_state(chgdst.iistate);
+	release_change_context(&chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3980,7 +3941,7 @@ repack_worker_internal(dsm_segment *seg)
 
 	for (;;)
 	{
-		bool	stop = decode_concurrent_changes(decoding_ctx, shared);
+		bool		stop = decode_concurrent_changes(decoding_ctx, shared);
 
 		if (stop)
 			break;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72f7c0b992b..12bfd50d25b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -426,7 +426,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
-ChangeDest
+ChangeContext
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0006-rename-routines-on-the-logical-output-plugin-sid.patch"



^ permalink  raw  reply  [nested|flat] 457+ messages in thread


end of thread, other threads:[~2026-03-23 20:27 UTC | newest]

Thread overview: 457+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2022-03-27 16:55 [PATCH 2/4] zstd: support long distance mode in pg_dump/basebackup Justin Pryzby <pryzbyj@telsasoft.com>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>
2026-03-23 20:27 [PATCH v44 05/10] Rename ChangeDest to ChangeContext, and absorb IndexInsertState Álvaro Herrera <alvherre@kurilemu.de>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox